import java.awt.Font;
import java.text.DecimalFormat;
import java.util.ArrayList;

import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.awt.GLJPanel;
import javax.media.opengl.glu.GLU;
import com.jogamp.opengl.util.awt.TextRenderer;

import edu.umd.fosite.geom.TriangleMeshSurface;
import edu.umd.fosite.graphics.Drawer;
import edu.umd.fosite.graphics.GLTriangleMeshSurfaceRenderer;

/**
 * This class manages the window
 * 
 * @author Daniel Kendix (dkendix@gmail.com)
 */
@SuppressWarnings("serial")
public class World extends GLJPanel {
	ArrayList<Building> buildings = new ArrayList<Building>();
	DecimalFormat df = new DecimalFormat("#.##");
	TextRenderer renderer;
	int dim;
	double min; // Was used for old surface generation
	double max;
	double field = 60; // Field of view angle
	double veiw; // View distance
	double currentTime = System.currentTimeMillis();
	double interval = System.currentTimeMillis() - currentTime;
	Player p;
	TriangleMeshSurface surface;
	private GLTriangleMeshSurfaceRenderer surfaceRenderer;
	private Drawer<GL2> surfaceDrawer;
	GLU glu = new GLU();

	/**
	 * Constructor
	 * 
	 * @param defaultCapab
	 */
	public World(GLCapabilities defaultCapab, int dimen) {
		super(defaultCapab);
		dim = dimen;
		veiw = 300;

		surface = TriangleMeshSurface.generateDiamondSquare(0.5);
		surfaceRenderer = new GLTriangleMeshSurfaceRenderer();
		generateBuildings(80, 80);
		p = new Player(100, 100, 100);

		addGLEventListener(new GLEventListener() {
			public void display(GLAutoDrawable drawable) {

				/* TEXT ON SCREEN */
				render(drawable.getGL().getGL2(), drawable.getWidth(),
						drawable.getHeight());
				renderer.beginRendering(drawable.getWidth(),
						drawable.getHeight());
				// optionally set the color
				renderer.setColor(1.0f, 0.2f, 0.2f, 0.8f);
				renderer.draw(
						"Camera: " + df.format(p.posx) + ", "
								+ df.format(p.posy) + ", " + df.format(p.posz),
						0, 0);
				// ... more draw commands, color changes, etc.
				renderer.endRendering();

			}

			public void init(GLAutoDrawable drawable) {
				GL2 gl = drawable.getGL().getGL2();
				gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

				// make sure hidden faces aren't drawn
				gl.glEnable(GL.GL_DEPTH_TEST);
				gl.glDepthFunc(GL.GL_LEQUAL);

				surfaceDrawer = surfaceRenderer.render(gl, surface);
			}

			public void dispose(GLAutoDrawable drawable) {

			}

			public void reshape(GLAutoDrawable drawable, int x, int y,
					int width, int height) {

				setup(drawable.getGL().getGL2(), width, height);

			}

		});
	}

	/**
	 * Setup method
	 * 
	 * @param gl2
	 * @param width
	 * @param height
	 */
	public void setup(GL2 gl, int width, int height) {
		// Set up antialiasing
		gl.glEnable(GL2.GL_BLEND);
		// gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE_MINUS_SRC_ALPHA);
		// gl.glBlendFunc(GL2.GL_SRC_ALPHA, GL2.GL_ONE);
		gl.glEnable(GL2.GL_POINT_SMOOTH);
		gl.glEnable(GL2.GL_LINE_SMOOTH);
		gl.glHint(GL2.GL_POLYGON_SMOOTH_HINT, GL2.GL_NICEST);

		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();

		gl.glEnable(GL2.GL_NORMALIZE);

		/* SETTING UP LIGHTS */
		gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_DIFFUSE,
				new float[] { 1f, 1f, 1f, 1 }, 0);
		gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_AMBIENT,
				new float[] { 1f, 1f, 1f, 1 }, 0);
		gl.glLightfv(GL2.GL_LIGHT0, GL2.GL_POSITION, new float[] { dim / 2,
				1000, dim / 2, 1 }, 0);

		gl.glEnable(GL2.GL_LIGHTING);
		gl.glEnable(GL2.GL_LIGHT0);

		gl.glEnable(GL2.GL_SMOOTH);
		gl.glShadeModel(GL2.GL_SMOOTH);
		gl.glPolygonMode(GL2.GL_FRONT_AND_BACK, GL2.GL_FILL);
		gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER,
				GL.GL_LINEAR);

		gl.glViewport(0, 0, width, height);

	}

	/**
	 * Renders everything
	 * 
	 * @param gl
	 * @param width
	 * @param height
	 */
	private void resetPerspective(GL2 gl2) {

		interval = (System.currentTimeMillis() - currentTime);
		p.update(interval / 1000, surface);
		currentTime += interval;

		gl2.glMatrixMode(GL2.GL_PROJECTION);
		gl2.glLoadIdentity();
		gl2.glClearColor(0f, 0f, 0f, 0f); // black

		GLU glu = new GLU();

		/*
		 * Code to move the camera (it is in the same place as the player for
		 * now)
		 */

		glu.gluPerspective(field, 1.0, 1.0, 300);
		glu.gluLookAt(p.posx, p.posy, p.posz, p.posx + p.d * p.a, p.posy + p.c,
				p.posz - p.d * p.b, 0, 1, 0); // good

	}

	public void render(GL2 gl, int width, int height) {
		resetPerspective(gl);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
		gl.glLoadIdentity();
		renderer = new TextRenderer(new Font("SansSerif", Font.BOLD, 12));
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

		for (Building a : buildings)
			a.render(gl);

		/* AXIS */
		gl.glBegin(GL.GL_LINES);
		gl.glVertex3d(0, 0, 1000);
		gl.glVertex3d(0, 0, -1000);
		gl.glEnd();
		gl.glBegin(GL.GL_LINES);
		gl.glVertex3d(0, 1000, 0);
		gl.glVertex3d(0, -1000, 0);
		gl.glEnd();
		gl.glBegin(GL.GL_LINES);
		gl.glVertex3d(1000, 0, 0);
		gl.glVertex3d(-1000, 0, 0);
		gl.glEnd();

		/* DIFFERENT DRAWING METHODS IN SURFACE USED FOR TESTING */
		// ter.drawHeights(gl);
		// ter.drawConnectSquare(gl, p.posx, p.posz, veiw);
		// ter.drawFilled(gl, p.posx, p.posz, veiw);
		// ter.drawVectorArrays(gl);

		gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_DIFFUSE, new float[] { .5f, .5f,
				.5f, 1f }, 0);
		gl.glMaterialfv(GL2.GL_FRONT, GL2.GL_AMBIENT, new float[] { 0f, 0f, 0f,
				1f }, 0);
		surfaceDrawer.draw();
	}

	private void generateBuildings(int spacingX, int spacingZ) {
		for (int i = 0; i < dim - spacingX; i += spacingX)
			for (int j = 0; j < dim - spacingZ; j += spacingZ) {
				int maxY = Integer.MIN_VALUE;
				for (int a = i; a < i + spacingX; a++)
					for (int b = j; b < j + spacingZ; b++) {
						maxY = (int) Math.max(maxY, surface.at(a, b));
					}
				buildings.add(new Building(new Point(i, maxY, j),
						(int) (spacingX * .8), 100, (int) (spacingZ * .8),
						surface));
			}

	}

}
